package org.diy.traffic.sources;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.diy.traffic.updates.TrafficUpdate;
import org.diy.traffic.util.TLog;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

import winterwell.jtwitter.TwitterException;
import winterwell.jtwitter.TwitterForbiddenException;
import winterwell.jtwitter.TwitterRateLimitException;


public class BlueGhostTrafficSource extends TrafficSource {
	
	/**
	 * This is a identifier unique to this TrafficSource subclass which should be prepended to
	 * any individual instance names when building a unique identifier string.
	 */
	private static String sourceType = "BLUEGHOST_SOURCE";
	
	/**
	 * This HashMap holds a list of instances of YahooTrafficSources that have been created. myInstances maps
	 * a unique string identifier to an instance of a YahooTrafficSource. The reason for this is that it helps
	 * keep track of all the cities which are being tracked and could facilitate batch operations all of them.
	 */
	private static HashMap<String, BlueGhostTrafficSource> myInstances = 
		new HashMap<String, BlueGhostTrafficSource>();
	
	/**
	 * An arraylist of recent updates. This is used to compare any new updates against so that this source
	 * is not posting the same updates multiple times.
	 */
	private ArrayList<TrafficUpdate> recentUpdates = new ArrayList<TrafficUpdate>();
		
	/**
	 * This is the base string for the Yahoo! Traffic REST API.
	 */
	private String requestBase = "http://bbc.blueghost.co.uk/trafficGeoRss.php?";

	/** Used to build queries to the BlueGhost Traffic API **/
	private final String PARAM_LOCATION     = "location";
	
	/** The city which this YahooTrafficSource represents **/
	private String cityName;
		
	/**
	 * Constructor for a BlueGhost traffic source. 
	 * @param city   The city which this YahooTrafficSource represents
	 */
	public BlueGhostTrafficSource(String city) {
		// this constructor does not take a update frequency, so use the default
		this(city, TrafficSource.DEFAULT_UPDATE_FREQUENCY_S); //900 seconds is 15 minutes;
	}
	
	/**
	 * Constructor for YahooTrafficSource which takes city, state and updateFrequency
	 * @param city		The city which this YahooTrafficSource represents
	 * @param state		The state which this YahooTrafficSource represents
	 * @param updateFrequency A value in seconds which represents how often this source should be updated
	 */
	public BlueGhostTrafficSource(String city, int updateFrequency) {
		super(TrafficSource.encodeAddress(new String[]{city}), updateFrequency);
		this.cityName  = city;
		// create this source's unique name by appending the address info to the base sourcetype
		this.sourceName = BlueGhostTrafficSource.sourceType + "_" + TrafficSource.encodeAddress(new String[]{city});
	}
		
	/**
	 * This function does the bulk of the work in checking Yahoo! for traffic updates.
	 * @param updateLastCheck true if this source's last check should be updated to now
	 * @return A list of TrafficUpdate objects, which could be empty if there are no updates.
	 * @throws YahooTrafficException if something goes wrong while checking for updates
	 */
	@Override
	public List<TrafficUpdate> doCheck(boolean updateLastCheck) throws BlueGhostTrafficException {
		
		// will contain the TrafficUpdate objects, if there are any
		ArrayList<TrafficUpdate> trafficUpdates = new ArrayList<TrafficUpdate>();
		
		// build the HTTP request
		String request = requestBase;
		try {
			request += (PARAM_LOCATION  + "=" + URLEncoder.encode(cityName, "UTF-8"));
		} catch (UnsupportedEncodingException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
			throw new BlueGhostTrafficException("Unable to urlencode city " + cityName);
		}
		
		System.err.println("URL here is " + request);
		
		// Create a new GET with the built request
        HttpClient client = new HttpClient();
        GetMethod method = new GetMethod(request);
        
        // Send GET request
        int statusCode;
        try {
        	statusCode = client.executeMethod(method);
        } catch (HttpException he) {
        	throw new BlueGhostTrafficException("Error executing \"GET\": " + he.getMessage());
        } catch (IOException ioe) {
        	throw new BlueGhostTrafficException("Error executing \"GET\": " + ioe.getMessage());
        }
        
        if (statusCode != HttpStatus.SC_OK) {
        	// if something has gone wrong ....
        	TLog.log(Level.SEVERE, "Method failed: " + method.getStatusLine());
        	throw new BlueGhostTrafficException("Error fetching BlueGhost Traffic page");
        }
        
        InputStream rstream = null;
        
        // Get the response body
        try {
        	rstream = method.getResponseBodyAsStream();
        } catch (IOException ioe) {
        	throw new BlueGhostTrafficException("Error getting \"GET\" response body: " + ioe.getMessage());
        }
        	
        // Process response
        Document response;
        try {
        	response = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(rstream);
        } catch (Exception e){
        	throw new BlueGhostTrafficException("Exception [" + e.getClass()+ "] while building XML doc: " + e.getMessage());
        } 
        
        // A bunch of XML parsing....
        XPathFactory factory = XPathFactory.newInstance();
        XPath xPath=factory.newXPath();
        
        //Get all search Result nodes
        
        NodeList nodes;
        try {
        	// Get each <ResultSet><Result> node
        	nodes = (NodeList)xPath.evaluate("/RDF/item", response, XPathConstants.NODESET);
        } catch (XPathExpressionException xpee)  {
        	try {
        		// if something went wrong getting the Results, perhaps Yahoo reported an error?
        		nodes = (NodeList) xPath.evaluate("/Error/Message", response, XPathConstants.NODESET);
        	} catch (XPathExpressionException xpee2){
        		// if you can't even find the <Error><Message> I dont know w.t.f. happened
        		throw new BlueGhostTrafficException("Don't understand BlueGhost response" + "[" + xpee.getMessage() + "]");
        	}
        	// didn't get any <Result>s most likely because it couldn't find your location
        	throw new BlueGhostTrafficException("Can't find location " + "[" + xpee.getMessage() + "]");
        }
          
        //Iterate over search Result nodes 
        int nodeCount = nodes.getLength();
        for (int i = 0; i < nodeCount; i++) {
           
        	//Get each xpath expression as a string
        	String title, description;
        	try {
        		// The title is a succinct representation of the traffic update, so don't worry about the description element 
        		title = (String)xPath.evaluate("title", nodes.item(i), XPathConstants.STRING);
        		description = (String)xPath.evaluate("description", nodes.item(i), XPathConstants.STRING);
        	} catch (XPathExpressionException xpee) {
        		throw new BlueGhostTrafficException("Can't find location " + "[" + xpee.getMessage() + "]");
        	}
        	
        	// Create a new TrafficUpdate and add it to the List. As arguments, it takes a reference to the source
        	// (this), a timestamp, and an actual update message, for which we use title we scraped from XML.
        	trafficUpdates.add(new TrafficUpdate(this, System.currentTimeMillis(), description ));
         }
        
        if (updateLastCheck) {
        	// if we're supposed to update, then use the utility function to set lastcheck to now.
        	setLastCheckToNow();
        }

        
        //IMPORTANT!! To make sure that the source isn't repeatedly posting the same updates, they must go
        // through a reconciliation process. This function takes care of weeding out any updates which have stayed
        // posted on the server (Yahoo in this case) which I've already posted....
        reconcileNewAndRecentUpdates(trafficUpdates);
        
        return trafficUpdates;
	}
	
	
	/**
	 * Helper function to print out a list of TrafficUpdates
	 * @param l the list of TrafficUpdates to print.
	 */
	private void printUpdates(ArrayList<TrafficUpdate> l) {
		Iterator<TrafficUpdate> i = l.iterator();
		while (i.hasNext()) {
			TLog.log(Level.FINEST, (i.next()).toString());
		}
	}
	
	/**
	 * A class which represents an exceptional state which occurred during the processing of Yahoo! traffic updates.
	 * Inherits from TrafficSourceException class.
	 * @author Dan Greenblatt
	 *
	 */
	public class BlueGhostTrafficException extends TrafficSourceException {
		
		public BlueGhostTrafficException() {super();}
		
		public BlueGhostTrafficException(Exception e) {super(e);}
		
		public BlueGhostTrafficException(String s) {super(s);}
	}
}
